package com.java.cg.search.service;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.java.cg.goods.entity.Sku;
import com.java.cg.goods.feign.GoodsApi;
import com.java.cg.search.entity.SkuInfo;
import com.java.cg.search.repository.SkuInfoRepository;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import java.util.*;

/**
 * @author jiangli
 * @since 2020/2/14 16:49
 */
@Service
public class SearchService {
	@Autowired
	private SkuInfoRepository skuInfoRepository;
	@Autowired
	private GoodsApi goodsApi;
	@Autowired
	private ElasticsearchTemplate elasticsearchTemplate;


	public void importSku() {
		int page = 1;
		int rows = 1000;

		do {
			//分页查询全部上架的spu
			Sku sku = new Sku();
			sku.setStatus("1");
			List<Sku> skus = goodsApi.search(page, rows, sku);
			if (skus.size() == 0) {
				break;
			}

			//skus ==> skuInfos
			List<SkuInfo> skuInfos = JSON.parseArray(JSON.toJSONString(skus), SkuInfo.class);
			// 将spec转成specMap
			skuInfos.forEach(skuInfo -> {
				Map<String, Object> map = JSON.parseObject(skuInfo.getSpec(), Map.class);
				skuInfo.setSpecMap(map);
			});
			skuInfoRepository.saveAll(skuInfos);

			page++;
			rows = skus.size();
		} while (rows == 1000);

	}

	/***
	 * 搜索
	 */
	public Map<String, Object> search(Map<String, String> searchMap) {
		Map<String, Object> resultMap = new HashMap<>();

		//1.设置查询的条件
		NativeSearchQueryBuilder nativeSearchQueryBuilder = buildBasicQuery(searchMap);

		//聚合查询--商品分类
		//addAggregation():添加聚合查询
		//1)取别名
		//2)表示根据哪个域进行分组查询
		//用户没有选择分类则查询出分类信息
/*		if (CollectionUtil.isEmpty(searchMap) || StrUtil.isEmpty(searchMap.get("category"))) {
			List<String> categoryList = getStringsCategoryList(nativeSearchQueryBuilder);
			resultMap.put("categoryList", categoryList);
		}

		//聚合查询--品牌名称
		if (CollectionUtil.isEmpty(searchMap) || StrUtil.isEmpty(searchMap.get("brand"))) {
			List<String> brandList = getStringsBrandList(nativeSearchQueryBuilder);
			resultMap.put("brandList", brandList);
		}

		//聚合查询--商品的规格
		Map<String, Set<String>> specMap = getStringSetMap(nativeSearchQueryBuilder);
		resultMap.put("specMap", specMap);*/

		Map<String, Object> groupSearchMap = getGroupSearchMap(nativeSearchQueryBuilder, searchMap);

		//2.搜索关键字设置高亮
		HighlightBuilder.Field field = new HighlightBuilder.Field("name"); //指定高亮域
		//前缀 <em style='color:red;'>
		field.preTags("<em style='color:red;'>").postTags("</em>").fragmentSize(100); //碎片长度,关键词的长度
		nativeSearchQueryBuilder.withHighlightFields(field);

		//3.执行查询
		AggregatedPage<SkuInfo> skuPage = elasticsearchTemplate.queryForPage(
				nativeSearchQueryBuilder.build(), //搜索条件
				SkuInfo.class,                    //数据集合要转换的类型
				new SearchResultMapper() { //执行搜索后将结果集封装到该对象中

					@Override
					public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
						List<T> content = new ArrayList<>();
						//获取所有数据,高亮和非高亮
						for (SearchHit hit : searchResponse.getHits()) {
							//获取非高亮数据
							SkuInfo skuInfo = JSON.parseObject(hit.getSourceAsString(), SkuInfo.class);
							//获取高亮数据
							HighlightField highlightField = hit.getHighlightFields().get("name");
							if (highlightField != null && highlightField.getFragments() != null) {
								//取出高亮数据
								Text[] fragments = highlightField.getFragments();
								//StringBuilder线程不安全 StringBuffer线程安全
								StringBuilder sb = new StringBuilder();
								for (Text fragment : fragments) {
									sb.append(fragment.toString());
								}
								//高亮替换非高亮
								skuInfo.setName(sb.toString());
							}
							content.add((T) skuInfo);
						}
						//1)搜索的集合数据:(携带高亮)List<T> content
						//2)分页对象:Pageable pageable
						//3)搜索记录的总条数:long total
						return new AggregatedPageImpl<>(content, pageable, searchResponse.getHits().getTotalHits(), searchResponse.getScrollId());
					}
				});

		//4.返回结果
		resultMap.put("rows", skuPage.getContent());
		resultMap.put("total", skuPage.getTotalElements());
		resultMap.put("totalPages", skuPage.getTotalPages());
		resultMap.putAll(groupSearchMap);

		//获取分页参数给前端分页使用
		Pageable pageable = nativeSearchQueryBuilder.build().getPageable();
		resultMap.put("pageNum", pageable.getPageNumber()); //当前页
		resultMap.put("pageSize", pageable.getPageSize()); //每页显示多少条
		return resultMap;
	}

	/**
	 * 获取分组列表数据
	 */
	private Map<String, Object> getGroupSearchMap(NativeSearchQueryBuilder nativeSearchQueryBuilder, Map<String, String> searchMap) {
		Map<String, Object> resultMap = new HashMap<>();
		//设置查询的条件
		if (CollectionUtil.isEmpty(searchMap) || StrUtil.isEmpty(searchMap.get("category"))) {
			nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("categoryNameAgg").field("categoryName").size(1000));
		}
		if (CollectionUtil.isEmpty(searchMap) || StrUtil.isEmpty(searchMap.get("brand"))) {
			nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("brandNameAgg").field("brandName").size(1000));
		}
		nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("specAgg").field("spec.keyword").size(1000));

		//执行查询
		AggregatedPage<SkuInfo> skuPage = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class);

		//获取聚合结果
		if (CollectionUtil.isEmpty(searchMap) || StrUtil.isEmpty(searchMap.get("category"))) {
			StringTerms categoryNameAgg = (StringTerms) skuPage.getAggregation("categoryNameAgg");
			List<String> categoryList = getResultFromStringTerms(categoryNameAgg);
			resultMap.put("categoryList", categoryList);
		}
		if (CollectionUtil.isEmpty(searchMap) || StrUtil.isEmpty(searchMap.get("brand"))) {
			StringTerms brandNameAgg = (StringTerms) skuPage.getAggregation("brandNameAgg");
			List<String> brandList = getResultFromStringTerms(brandNameAgg);
			resultMap.put("brandList", brandList);
		}

		StringTerms spec = (StringTerms) skuPage.getAggregation("specAgg");
		Map<String, Set<String>> specMap = getSpecMap(spec);
		resultMap.put("specMap", specMap);

		return resultMap;

	}

	private List<String> getResultFromStringTerms(StringTerms stringTerms) {
		List<String> groupSearchList = new ArrayList<>();
		if (stringTerms != null) {
			for (StringTerms.Bucket bucket : stringTerms.getBuckets()) {
				String fieldName = bucket.getKeyAsString();//分类的名称
				groupSearchList.add(fieldName);
			}
		}
		return groupSearchList;
	}


	/**
	 * 获取规格列表数据
	 */
	private Map<String, Set<String>> getSpecMap(StringTerms spec) {
/*		//1.设置查询的条件
		nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("specAgg").field("spec.keyword").size(1000));
		//2.构建查询对象
		NativeSearchQuery query = nativeSearchQueryBuilder.build();
		//3.执行查询
		AggregatedPage<SkuInfo> skuPage = elasticsearchTemplate.queryForPage(query, SkuInfo.class);
		//4.获取分组结果
		StringTerms spec = (StringTerms) skuPage.getAggregation("specAgg");*/

		Map<String, Set<String>> specMap = new HashMap<>();
		//得到去重的spec字符串集合
		Set<String> specList = new HashSet<>();
		if (spec != null) {
			for (StringTerms.Bucket bucket : spec.getBuckets()) {
				specList.add(bucket.getKeyAsString());
			}
		}
		//将spec字符串集合转换成map
		for (String specjson : specList) {
			//{"手机屏幕尺寸":"5寸","网络":"联通2G","颜色":"红","测试":"s11","机身内存":"16G","存储":"16G","像素":"800万像素"}
			Map<String, String> map = JSON.parseObject(specjson, Map.class);
			for (Map.Entry<String, String> entry : map.entrySet()) {
				String key = entry.getKey();        //规格名字
				String value = entry.getValue();    //规格选项值
				//获取当前规格名字对应的规格数据
				Set<String> specValues = specMap.get(key);
				if (CollectionUtil.isEmpty(specValues)) {
					specValues = new HashSet<>();
				}
				//将当前规格加入到集合中
				specValues.add(value);
				//将数据存入到specMap中
				specMap.put(key, specValues);
			}
		}
		return specMap;
	}

	/**
	 * 获取品牌列表数据
	 */
	private List<String> getStringsBrandList(NativeSearchQueryBuilder nativeSearchQueryBuilder) {
		//1.设置查询的条件
		nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("brandNameAgg").field("brandName").size(1000));
		//2.构建查询对象
		NativeSearchQuery query = nativeSearchQueryBuilder.build();
		//3.执行查询
		AggregatedPage<SkuInfo> skuPage = elasticsearchTemplate.queryForPage(query, SkuInfo.class);
		//4.获取分组结果
		StringTerms brandNameAgg = (StringTerms) skuPage.getAggregation("brandNameAgg");
		List<String> brandList = new ArrayList<>();
		if (brandNameAgg != null) {
			for (StringTerms.Bucket bucket : brandNameAgg.getBuckets()) {
				String brandName = bucket.getKeyAsString();//品牌名称
				brandList.add(brandName);
			}
		}
		return brandList;
	}

	/**
	 * 获取分类列表数据
	 */
	private List<String> getStringsCategoryList(NativeSearchQueryBuilder nativeSearchQueryBuilder) {
		//1.设置查询的条件
		nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("categoryNameAgg").field("categoryName").size(1000));
		//2.构建查询对象
		NativeSearchQuery query = nativeSearchQueryBuilder.build();
		//3.执行查询
		AggregatedPage<SkuInfo> skuPage = elasticsearchTemplate.queryForPage(query, SkuInfo.class);
		//4.获取分组结果
		StringTerms categoryNameAgg = (StringTerms) skuPage.getAggregation("categoryNameAgg");
		List<String> categoryList = new ArrayList<>();
		if (categoryNameAgg != null) {
			for (StringTerms.Bucket bucket : categoryNameAgg.getBuckets()) {
				String categoryName = bucket.getKeyAsString();//分类的名称
				categoryList.add(categoryName);
			}
		}
		return categoryList;
	}

	/**
	 * 构建搜索条件
	 */
	private NativeSearchQueryBuilder buildBasicQuery(Map<String, String> searchMap) {
		//创建查询对象
		NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();

		//组合查询
		BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
		if (CollectionUtil.isNotEmpty(searchMap)) {
			//获取关键字的值
			String keywords = searchMap.get("keywords");
			if (StrUtil.isNotEmpty(keywords)) {
				boolQueryBuilder.must(QueryBuilders.matchQuery("name", keywords));
			}
			//分类搜索
			if (StrUtil.isNotEmpty(searchMap.get("category"))) {
				boolQueryBuilder.must(QueryBuilders.termQuery("categoryName", searchMap.get("category")));
			}
			//品牌搜索
			if (StrUtil.isNotEmpty(searchMap.get("brand"))) {
				boolQueryBuilder.must(QueryBuilders.termQuery("brandName", searchMap.get("brand")));
			}
			//规格过滤查询: url?spec_网络=联通&spec_颜色=红
			for (String key : searchMap.keySet()) {
				//以spec_开头的表示规格参数
				if (key.startsWith("spec_")) {
					String value = searchMap.get(key).replace("\\", "");
					//在es中保存的类型是specMap.xxx.keyword
					boolQueryBuilder.must(QueryBuilders.termQuery("specMap." + key.substring(5) + ".keyword", value));
				}
			}
			//价格区间搜索 0-500元 500-2000元 20000以上
			String price = searchMap.get("price");
			if (StrUtil.isNotEmpty(price)) {
				price = price.replace("元", "").replace("以上", "");
				//分割
				String[] prices = price.split("-");
				if (ArrayUtil.isNotEmpty(prices)) {
					boolQueryBuilder.must(QueryBuilders.rangeQuery("price").gt(Integer.parseInt(prices[0]))); //大于
					if (price.length() == 2) {
						boolQueryBuilder.must(QueryBuilders.rangeQuery("price").lte(Integer.parseInt(prices[1]))); //小于等于
					}
				}
			}
			//排序
			String sort = searchMap.get("sort"); //排序域(字段)
			String order = searchMap.get("order"); //排序规则
			if (StrUtil.isNotEmpty(order) && StrUtil.isNotEmpty(sort)) {
				nativeSearchQueryBuilder.withSort(new FieldSortBuilder(sort).order(SortOrder.valueOf(order.toUpperCase())));
			}
		}
		//分页
		Integer page = getPage(searchMap);
		Integer size = 50;
		nativeSearchQueryBuilder.withPageable(PageRequest.of(page - 1, size));

		nativeSearchQueryBuilder.withQuery(boolQueryBuilder);
		return nativeSearchQueryBuilder;
	}

	private Integer getPage(Map<String, String> searchMap) {
		try {
			if (StrUtil.isNotEmpty(searchMap.get("pageNum"))) {
				return Integer.parseInt(searchMap.get("pageNum"));
			}
		} catch (NumberFormatException e) {
			e.printStackTrace();
		}
		return 1;
	}

	private void createIndex() {
		//删除索引
		elasticsearchTemplate.deleteIndex(SkuInfo.class);
		//创建索引
		elasticsearchTemplate.createIndex(SkuInfo.class);
		//创建映射
		elasticsearchTemplate.putMapping(SkuInfo.class);
	}
}
